package com.mico.xchange.chaoex.service;

import com.mico.micochange.common.RSACoder;
import com.mico.xchange.chaoex.ChaoexAuthenticated;
import com.mico.xchange.service.BaseParamsDigest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import si.mazi.rescu.Params;
import si.mazi.rescu.RestInvocation;

import javax.crypto.Mac;
import javax.ws.rs.FormParam;
import javax.ws.rs.QueryParam;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.mico.xchange.utils.DigestUtils.bytesToHex;

public class ChaoexHmacDigest extends BaseParamsDigest {

  private static final Logger LOG = LoggerFactory.getLogger(ChaoexHmacDigest.class);

  private final Field invocationUrlField;
  private String secretKey1;

  private ChaoexHmacDigest(String secretKey) {
    super(secretKey, HMAC_SHA_256);
    secretKey1 = secretKey;
    try {
      invocationUrlField = RestInvocation.class.getDeclaredField("invocationUrl");
      invocationUrlField.setAccessible(true);
    } catch (NoSuchFieldException e) {
      throw new IllegalStateException("rescu library has been updated");
    }
  }

  public static ChaoexHmacDigest createInstance(String secretKeyBase64) {
    return secretKeyBase64 == null ? null : new ChaoexHmacDigest(secretKeyBase64);
  }

  /** @return the query string except of the "signature" parameter */
  private static String getQuery(RestInvocation restInvocation) {
    final Params p = Params.of();
    restInvocation
        .getParamsMap()
        .get(QueryParam.class)
        .asHttpHeaders()
        .entrySet()
        .stream()
        .filter(e -> !ChaoexAuthenticated.SIGNATURE.equals(e.getKey()))
        .forEach(e -> p.add(e.getKey(), e.getValue()));
    return p.asQueryString();
  }

  @Override
  public String digestParams(RestInvocation restInvocation) {
    try {
      final String input;

      if (restInvocation.getPath().startsWith("wapi/")) {
        // little dirty hack for /wapi methods
        input = getQuery(restInvocation);
      } else {
        switch (restInvocation.getHttpMethod()) {
          case "GET":
          case "DELETE":
            input = getQuery(restInvocation);
            break;
          case "POST":
            input = restInvocation.getRequestBody();
            break;
          default:
            throw new RuntimeException(
                    "Not support http method: " + restInvocation.getHttpMethod());
        }
      }

//      Mac mac = getMac();
//      mac.update(input.getBytes("UTF-8"));
        String decodeInput = URLDecoder.decode(input);

      System.out.println("param chaoex:"+input);
      String signStr = "";
      String[] arr = decodeInput.split("&");
      for(String str : arr){
          String[] param = str.split("=");
          if("timestamp".equals(param[0])) {
              signStr = param[1];
              break;
          }
      }
      String printBase64Binary = RSACoder.sign(signStr.getBytes("UTF-8"),secretKey1);
      LOG.debug("value to sign: {},  signature: {}", signStr, printBase64Binary);

      // https://github.com/mmazi/rescu/issues/62
      // Seems rescu does not support ParamsDigest in QueryParam.
      // hack to replace the signature in the invocation URL.
      String invocationUrl = restInvocation.getInvocationUrl();
      LOG.debug("old invocationUrl: {}", invocationUrl);
      // String newInvocationUrl = UriBuilder.fromUri(invocationUrl).replaceQueryParam("signature",
      // printBase64Binary).build().toString();

      final String sig = "sign=";
      int idx = invocationUrl.indexOf(sig);
      String newInvocationUrl = invocationUrl.substring(0, idx + sig.length()) + printBase64Binary;
      try {
        invocationUrlField.set(restInvocation, newInvocationUrl);
      } catch (IllegalArgumentException | IllegalAccessException e) {
        throw new RuntimeException(e);
      }
      LOG.debug("new invocationUrl: {}", restInvocation.getInvocationUrl());
      return printBase64Binary;
    } catch (Exception e) {
      throw new RuntimeException("Illegal encoding, check the code.", e);
    }
  }

}
